﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using ConfigGen.Core.ConfigurationSettings;
using ConfigGen.Core.SettingsLoader;
using ConfigGen.Core.TemplateLoader;
using ConfigGen.Utilities.Extensions.System;
using log4net;

namespace ConfigGen.Core
{
    /// <summary>
    /// This class is responsible for orchestrating the overall config gen process. It starts by loading the config gen settings spreadsheet and 
    /// then calling <see cref="SingleFileGenerationController"/> for each machine specified in the spreadsheet (unless command line options dictate otherwise).
    /// </summary>
    public class ConfigurationFilesGenerator
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(ConfigurationFilesGenerator));

        //private readonly ISettingsLoader _settingsLoader;
        private readonly ITemplateLoader _templateLoader;
        private readonly IMachineConfigurationSettingCollectionFilter _machineConfigurationSettingCollectionFilter;
        private readonly IDefaultSettingsCombiner _defaultSettingsCombiner;
        private readonly IReadOnlyContainer _readOnlyContainer;

        private IEnumerable<SingleFileGenerationResults> _results;

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigurationFilesGenerator"/> class.
        /// </summary>
        /// <param name="defaultSettingsCombiner">Combines local and default settings</param>
        /// <param name="templateLoader">The template loader.</param>
        /// <param name="machineConfigurationSettingCollectionFilter">ConfigurationFilesGenerator helper class.</param>
        /// <param name="readOnlyContainer">The read only container.</param>
        /// <exception cref="ArgumentNullException">Raised if any argument is null.</exception>
        public ConfigurationFilesGenerator(
            IDefaultSettingsCombiner defaultSettingsCombiner,
            ITemplateLoader templateLoader,
            IMachineConfigurationSettingCollectionFilter machineConfigurationSettingCollectionFilter,
            IReadOnlyContainer readOnlyContainer)
        {
            //if (settingsLoader == null) throw new ArgumentNullException("settingsLoader");
            if (templateLoader == null) throw new ArgumentNullException("templateLoader");
            if (machineConfigurationSettingCollectionFilter == null) throw new ArgumentNullException("machineConfigurationSettingCollectionFilter");
            if (readOnlyContainer == null) throw new ArgumentNullException("readOnlyContainer");

            //_settingsLoader = settingsLoader;
            _defaultSettingsCombiner = defaultSettingsCombiner;
            _readOnlyContainer = readOnlyContainer;
            _templateLoader = templateLoader;
            _machineConfigurationSettingCollectionFilter = machineConfigurationSettingCollectionFilter;
        }

        /// <summary>
        /// Generates the configuration files according to the loaded settings template and any other configuration options.
        /// </summary>
        /// <returns>Number of files generated.</returns>
        public int GenerateFiles(Preferences preferences)
        {
            if (preferences == null) throw new ArgumentNullException("preferences");

            if (preferences.DumpSettingsOnly)
            {
                if (preferences.DumpSettingsFile.IsNullOrEmpty())
                {
                    Log.Warn("Option 'DumpSettingsOnly' was ignored as no 'DumpSettingsFile' setting was supplied.");
                }
                else
                {
                    DumpSettings(preferences);
                    Log.Warn("Option 'DumpSettingsOnly' was specified. Settings dumped. Program exiting.");
                }

                return 0;
            }

            ValidateConfigurationSettings(preferences);

            _templateLoader.LoadTemplate(new[] { preferences.TemplateFile });

            var settingsExtension = new FileInfo(preferences.SettingsFile).Extension;
            var settingsLoader = _readOnlyContainer.Resolve<ISettingsLoader>(settingsExtension);
            var settings = settingsLoader.LoadSettings(new[] { preferences.SettingsFile });

            if (!string.IsNullOrEmpty(preferences.DefaultSettingsFile) && File.Exists(preferences.DefaultSettingsFile))
            {
                var defaultSettingsExtension = new FileInfo(preferences.DefaultSettingsFile).Extension;
                var defaultSettingsLoader = _readOnlyContainer.Resolve<ISettingsLoader>(defaultSettingsExtension);
                var defaultSettings = defaultSettingsLoader.LoadSettings(new[] {preferences.DefaultSettingsFile});
                settings = _defaultSettingsCombiner.Combine(settings, defaultSettings);
            }
            
            var filteredSettings = _machineConfigurationSettingCollectionFilter.Filter(
                settings.MachineConfigurationSettings,
                preferences);

            var results = new ConcurrentBag<SingleFileGenerationResults>();

            //var parallelLoopResult = Parallel.ForEach(filteredSettings, (machine) => GenerateSingleConfigFile(configurationSettings, results, machine));

            foreach (var machine in filteredSettings)
            {
                GenerateSingleConfigFile(preferences, results, machine);
            }

            _results = results;

            return results.Count;
        }

        private void GenerateSingleConfigFile(
            Preferences preferences, 
            ConcurrentBag<SingleFileGenerationResults> results,
            MachineConfigurationSettings machine)
        {
            var singleFileGenerator = _readOnlyContainer.Resolve<ISingleFileGenerationController>();
            var result = singleFileGenerator.GenerateFile(_templateLoader, machine, preferences);
            results.Add(result);
        }

        private void ValidateConfigurationSettings(Preferences configurationSettings)
        {
            if (configurationSettings.SettingsFile.IsNullOrEmpty())
            {
                throw new PreferencesException("Settings file must be specified", "SettingsFile");
            }

            if (configurationSettings.TemplateFile.IsNullOrEmpty())
            {
                throw new PreferencesException("Template file must be specified", "TemplateFile");
            }

            if (configurationSettings.OutputDirectory.IsNullOrEmpty())
            {
                throw new PreferencesException("Output directory must be specified", "OutputDirectory");
            }

            if (configurationSettings.OutputEncoding != null && !configurationSettings.RemoveRootElement)
            {
                throw new PreferencesException("Output encoding can only be specified when using the 'remove root element' option.", "OutputEncoding");
            }
        }

        /// <summary>
        /// Gets the results generated by a call to <see cref="GenerateFiles"/>.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if this is accessed before a call to <see cref="GenerateFiles"/></exception>
        public IEnumerable<SingleFileGenerationResults> Results
        {
            get
            {
                if (_results == null) throw new InvalidOperationException("GenerateFiles must be called before Results property is accessed.");
                return _results;
            }
        }

        private static void DumpSettings(Preferences settings)
        {
            var writerSettings = new XmlWriterSettings
                                     {
                                         Indent = true,
                                         IndentChars = "   "
                                     };

            using (var xmlWriter = XmlWriter.Create(settings.DumpSettingsFile, writerSettings))
            {
                settings.SerializeToXml(xmlWriter);
            }
        }
    }
}
